﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace WeDonekRpc.Helper.UAParser
{
    internal static class Parsers
    {
        // ReSharper disable once InconsistentNaming
        public static Func<string, OS> OS ( Regex regex, string osReplacement, string v1Replacement, string v2Replacement, string v3Replacement, string v4Replacement )
        {
            // For variable replacements to be consistent the order of the linq statements are important ($1
            // is only available to the first 'from X in Replace(..)' and so forth) so a a bit of conditional
            // is required to get the creations to work. This is backed by unit tests
            if ( v1Replacement == "$1" )
            {
                if ( v2Replacement == "$2" )
                {
                    return Create(regex, from v1 in Replace(v1Replacement, "$1")
                                         from v2 in Replace(v2Replacement, "$2")
                                         from v3 in Replace(v3Replacement, "$3")
                                         from v4 in Replace(v4Replacement, "$4")
                                         from family in Replace(osReplacement, "$5")
                                         select new OS(family, v1, v2, v3, v4));
                }

                return Create(regex, from v1 in Replace(v1Replacement, "$1")
                                     from family in Replace(osReplacement, "$2")
                                     from v2 in Replace(v2Replacement, "$3")
                                     from v3 in Replace(v3Replacement, "$4")
                                     from v4 in Replace(v4Replacement, "$5")
                                     select new OS(family, v1, v2, v3, v4));
            }

            return Create(regex, from family in Replace(osReplacement, "$1")
                                 from v1 in Replace(v1Replacement, "$2")
                                 from v2 in Replace(v2Replacement, "$3")
                                 from v3 in Replace(v3Replacement, "$4")
                                 from v4 in Replace(v4Replacement, "$5")
                                 select new OS(family, v1, v2, v3, v4));
        }

        public static Func<string, Device> Device ( Regex regex, string familyReplacement, string brandReplacement, string modelReplacement )
        {
            return Create(regex, from family in ReplaceAll(familyReplacement)
                                 from brand in ReplaceAll(brandReplacement)
                                 from model in ReplaceAll(modelReplacement)
                                 select new Device(family, brand, model));
        }

        public static Func<string, UserAgent> UserAgent ( Regex regex, string familyReplacement, string majorReplacement, string minorReplacement, string patchReplacement )
        {
            return Create(regex, from family in Replace(familyReplacement, "$1")
                                 from v1 in Replace(majorReplacement, "$2")
                                 from v2 in Replace(minorReplacement, "$3")
                                 from v3 in Replace(patchReplacement, "$4")
                                 select new UserAgent(family, v1, v2, v3));
        }

        private static Func<Match, IEnumerator<int>, string> Replace ( string replacement )
        {
            return replacement != null ? Select(_ => replacement) : Select();
        }

        private static Func<Match, IEnumerator<int>, string> Replace (
            string replacement, string token )
        {
            return replacement != null && replacement.Contains(token)
                 ? Select(s => s != null ? replacement.ReplaceFirstOccurence(token, s) : replacement)
                 : Replace(replacement);
        }

        private static readonly string[] _allReplacementTokens = new string[]
        {
                "$1","$2","$3","$4","$5","$6","$7","$8","$9",
        };

        private static Func<Match, IEnumerator<int>, string> ReplaceAll ( string replacement )
        {
            if ( replacement == null )
            {
                return Select();
            }

            static string ReplaceFunction ( string replacementString, string matchedGroup, string token )
            {
                return matchedGroup != null
                    ? replacementString.ReplaceFirstOccurence(token, matchedGroup)
                    : replacementString;
            }

            return ( m, num ) =>
            {
                string finalString = replacement;
                if ( finalString.Contains("$") )
                {
                    GroupCollection groups = m.Groups;
                    for ( int i = 0 ; i < _allReplacementTokens.Length ; i++ )
                    {
                        int tokenNumber = i + 1;
                        string token = _allReplacementTokens[i];
                        if ( finalString.Contains(token) )
                        {
                            string replacementText = string.Empty;
                            Group group;
                            if ( tokenNumber <= groups.Count && ( group = groups[tokenNumber] ).Success )
                            {
                                replacementText = group.Value;
                            }

                            finalString = ReplaceFunction(finalString, replacementText, token);
                        }
                        if ( !finalString.Contains("$") )
                        {
                            break;
                        }
                    }
                }
                return finalString;
            };
        }

        private static Func<Match, IEnumerator<int>, string> Select ()
        {
            return Select(v => v);
        }

        private static Func<Match, IEnumerator<int>, T> Select<T> ( Func<string, T> selector )
        {
            return ( m, num ) =>
            {
                if ( !num.MoveNext() )
                {
                    throw new InvalidOperationException();
                }

                GroupCollection groups = m.Groups; Group group;
                return selector(num.Current <= groups.Count && ( group = groups[num.Current] ).Success
                                ? group.Value : null);
            };
        }

        private static Func<string, T> Create<T> ( Regex regex, Func<Match, IEnumerator<int>, T> binder )
        {
            return input =>
            {
#if REGEX_MATCHTIMEOUT
                    try
                    {
                        var m = regex.Match(input);
                        var num = Generate(1, n => n + 1);
                        return m.Success ? binder(m, num) : default(T);
                    }
                    catch (RegexMatchTimeoutException)
                    {
                        // we'll simply swallow this exception and return the default (non-matched)
                        return default(T);
                    }
#else
                Match m = regex.Match(input);
                IEnumerator<int> num = Generate(1, n => n + 1);
                return m.Success ? binder(m, num) : default;
#endif
            };
        }

        private static IEnumerator<T> Generate<T> ( T initial, Func<T, T> next )
        {
            for ( T state = initial ; ; state = next(state) )
            {
                yield return state;
            }
            // ReSharper disable once FunctionNeverReturns
        }
    }
}
